﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Xna.Framework;

namespace TPL.Xna.Common
{
    public interface IAnimator
    {
        /// <summary>
        /// Animation initialization.
        /// </summary>
        void Initialize();

        /// <summary>
        /// Animation updating.
        /// </summary>
        /// <param name="gameTime"></param>
        void Update(GameTime gameTime);

        /// <summary>
        /// Attach an animator to its owner sprite.
        /// </summary>
        /// <param name="sprite"></param>
        /// <returns></returns>
        IAnimator AttachTo(Sprite sprite);

        /// <summary>
        /// Enable animator to work or not.
        /// </summary>
        bool Enable { get; set; }
    }

    public abstract class SpritePropertyAnimator<T> : IAnimator
    {
        private readonly FieldInfo _property;
        private readonly IList<Sprite> _sprites = new List<Sprite>();

        protected SpritePropertyAnimator(Expression<Func<Sprite, T>> property)
        {
            var memberEx = (MemberExpression)property.Body;
            _property = (FieldInfo)memberEx.Member;
            Enable = true;
        }

        protected int SpriteCount
        {
            get { return _sprites.Count; }
        }

        protected void SetValue(T value)
        {
            foreach (var sprite in _sprites)
                _property.SetValue(sprite, value);
        }

        protected void SetValue(Sprite sprite, T value)
        {
            _property.SetValue(sprite, value);
        }

        protected T GetValue(Sprite sprite)
        {
            return (T)_property.GetValue(sprite);
        }

        protected T GetValue()
        {
            return (T)_property.GetValue(_sprites[0]);
        }

        public abstract void Initialize();

        protected abstract void Update(ref T value, T delta);
        protected abstract T CalculateDelta();
        public virtual void Update(GameTime gameTime)
        {
            T delta = CalculateDelta();
            T value;
            foreach (var sprite in _sprites)
            {
                value = GetValue(sprite);
                Update(ref value, delta);
                SetValue(sprite, value);
            }
        }

        public virtual IAnimator AttachTo(Sprite sprite)
        {
            _sprites.Add(sprite);
            return this;
        }

        public bool Enable { get; set; }
    }
}